package com.thunder.ktv.scoreengine2test.ui;// SpectrumView.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;

import com.thunder.ktv.scoreengine2sdk.ScoreEngine2; // Assuming this import is correct

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ScoreView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    private static final String TAG = "ScoreView";

    // --- Configuration ---
    private static final int MAX_HISTORY_SIZE = 200; // Max data points to keep in history
    private static final long VISIBLE_TIME_WINDOW_MS = 90 * 1000; // Show last 30 seconds of data
    private static final int BACKGROUND_COLOR = 0xCCCCCCCC;;
    private static final int TEXT_COLOR = Color.BLACK;
    private static final float STROKE_WIDTH_DP = 2f;
    private static final int LEGEND_TEXT_SIZE_SP = 12;
    private static final int LEGEND_PADDING_DP = 8;
    private static final int LEGEND_SWATCH_SIZE_DP = 15;
    private static final int GRAPH_MARGIN_DP = 10; // Margin around the graph area

    // --- Drawing State ---
    private SurfaceHolder surfaceHolder;
    private Thread drawingThread;
    private volatile boolean isDrawing = false;
    private Paint linePaint;
    private Paint textPaint;
    private Paint legendPaint; // For legend color swatches

    // --- Data ---
    private final LinkedList<TimedScoreResult> scoreHistory = new LinkedList<>();
    private final List<ScoreTypeConfig> scoreTypesToDisplay = new ArrayList<>();

    // --- Dimensions (calculated in onSizeChanged or surfaceChanged) ---
    private float strokeWidthPx;
    private float legendTextSizePx;
    private float legendPaddingPx;
    private float legendSwatchSizePx;
    private float graphMarginPx;

    private float graphLeft, graphTop, graphRight, graphBottom, graphWidth, graphHeight;
    private float legendAreaHeight;


    // Helper class to store score with timestamp
    private static class TimedScoreResult {
        long timestamp;
        ScoreEngine2.ScoreResult result;

        TimedScoreResult(long timestamp, ScoreEngine2.ScoreResult result) {
            this.timestamp = timestamp;
            this.result = result;
        }
    }

    // Helper class to configure how each score type is drawn
    private static class ScoreTypeConfig {
        String name;
        int color;
        ScoreValueExtractor extractor;

        ScoreTypeConfig(String name, int color, ScoreValueExtractor extractor) {
            this.name = name;
            this.color = color;
            this.extractor = extractor;
        }
    }

    // Functional interface to get a specific score from ScoreResult
    @FunctionalInterface
    private interface ScoreValueExtractor {
        int extract(ScoreEngine2.ScoreResult result);
    }


    // --- Constructors ---
    public ScoreView(Context context) {
        super(context);
        init(context);
    }

    public ScoreView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public ScoreView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public ScoreView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);

        // Convert DP/SP to PX
        strokeWidthPx = dpToPx(context, STROKE_WIDTH_DP);
        legendTextSizePx = spToPx(context, LEGEND_TEXT_SIZE_SP);
        legendPaddingPx = dpToPx(context, LEGEND_PADDING_DP);
        legendSwatchSizePx = dpToPx(context, LEGEND_SWATCH_SIZE_DP);
        graphMarginPx = dpToPx(context, GRAPH_MARGIN_DP);

        // Initialize Paints
        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(strokeWidthPx);
        linePaint.setStrokeCap(Paint.Cap.ROUND);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(TEXT_COLOR);
        textPaint.setTextSize(legendTextSizePx);

        legendPaint = new Paint();
        legendPaint.setAntiAlias(true);
        legendPaint.setStyle(Paint.Style.FILL);

        // Define which scores to display and their colors
        // You can customize these names and colors
        scoreTypesToDisplay.add(new ScoreTypeConfig("总分数", Color.CYAN, r -> r.overallScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("音高", Color.RED, r -> r.pitchScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("亮度", Color.GREEN, r -> r.brightnessScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("节奏", Color.YELLOW, r -> r.rhythmScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("情感", Color.MAGENTA, r -> r.emotionScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("气息", Color.BLUE, r -> r.breathScore));
        scoreTypesToDisplay.add(new ScoreTypeConfig("音色", Color.parseColor("#FFA500"), r -> r.timbreScore)); // Orange

        setFocusable(true);
    }

    // --- Public API ---
    public void updateScore(ScoreEngine2.ScoreResult result) {
        if (result == null) { // Optional: handle or log null results
            Log.w(TAG, "Received null ScoreResult");
            return;
        }
        // if (result.isAllZero()) { // Optional: skip if all scores are zero (depends on your use case)
        //     Log.d(TAG, "Skipping zero score update");
        //     return;
        // }

        synchronized (scoreHistory) {
            scoreHistory.add(new TimedScoreResult(System.currentTimeMillis(), result));
            if (scoreHistory.size() > MAX_HISTORY_SIZE) {
                scoreHistory.removeFirst();
            }
        }
        // The drawing loop will pick up the new data automatically
    }

    // --- SurfaceHolder.Callback Implementation ---
    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "Surface created");
        isDrawing = true;
        drawingThread = new Thread(this);
        drawingThread.start();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
        Log.d(TAG, "Surface changed: " + width + "x" + height);
        // Recalculate graph dimensions here
        updateGraphDimensions(width, height);
    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        Log.d(TAG, "Surface destroyed");
        isDrawing = false;
        boolean retry = true;
        while (retry) {
            try {
                if (drawingThread != null) {
                    drawingThread.join();
                }
                retry = false;
            } catch (InterruptedException e) {
                Log.e(TAG, "Failed to join drawing thread", e);
                // Try again or handle error
            }
        }
        drawingThread = null;
    }

    // --- Runnable Implementation (Drawing Loop) ---
    @Override
    public void run() {
        while (isDrawing) {
            Canvas canvas = null;
            try {
                canvas = surfaceHolder.lockCanvas();
                if (canvas != null) {
                    synchronized (surfaceHolder) { // Or synchronize on scoreHistory if access is complex
                        drawFrame(canvas);
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "Error in drawing loop", e);
            } finally {
                if (canvas != null) {
                    try {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    } catch (Exception e) {
                        Log.e(TAG, "Error unlocking canvas", e);
                    }
                }
            }

            // Optional: Control frame rate slightly if CPU usage is too high
            // try { Thread.sleep(16); } catch (InterruptedException e) { break; }
        }
    }

    // --- Drawing Logic ---
    private void updateGraphDimensions(int viewWidth, int viewHeight) {
        // Calculate height needed for legend
        Paint.FontMetrics fm = textPaint.getFontMetrics();
        float textHeight = fm.descent - fm.ascent;
        legendAreaHeight = textHeight + 2 * legendPaddingPx; // Simple legend, one line

        graphLeft = graphMarginPx;
        graphTop = graphMarginPx;
        graphRight = viewWidth - graphMarginPx;
        // graphBottom = viewHeight - graphMarginPx - legendAreaHeight;
        graphBottom = viewHeight - legendAreaHeight - graphMarginPx;


        graphWidth = graphRight - graphLeft;
        graphHeight = graphBottom - graphTop;

        Log.d(TAG, "Graph Dims: L=" + graphLeft + ", T=" + graphTop + ", R=" + graphRight + ", B=" + graphBottom + " | W=" + graphWidth + ", H=" + graphHeight);
        Log.d(TAG, "Legend Area Height: " + legendAreaHeight);
    }


    private void drawFrame(Canvas canvas) {
        // 1. Clear canvas
        canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.CLEAR); // Clear with transparency if needed
        canvas.drawColor(BACKGROUND_COLOR);


        // 2. Get current data snapshot (within synchronized block)
        List<TimedScoreResult> currentScores;
        long latestTimestamp;
        synchronized (scoreHistory) {
            if (scoreHistory.isEmpty()) {
                drawEmptyState(canvas);
                return;
            }
            currentScores = new ArrayList<>(scoreHistory); // Create a copy for safe iteration
            latestTimestamp = currentScores.get(currentScores.size() - 1).timestamp;
        }

        if (graphWidth <= 0 || graphHeight <= 0) {
            Log.w(TAG, "Graph dimensions are invalid, skipping drawFrame.");
            // Optionally draw a message indicating an issue or wait for valid dimensions.
            // For now, just return to avoid division by zero or drawing errors.
            Paint errorPaint = new Paint();
            errorPaint.setColor(Color.RED);
            errorPaint.setTextSize(40);
            errorPaint.setTextAlign(Paint.Align.CENTER);
            canvas.drawText("Waiting for layout...", canvas.getWidth()/2f, canvas.getHeight()/2f, errorPaint);
            return;
        }

        // 3. Calculate scaling factors
        float pixelsPerMs = graphWidth / (float) VISIBLE_TIME_WINDOW_MS;
        float pixelsPerScorePoint = graphHeight / 100.0f; // Scores are 0-100

        // 4. Draw each score line
        Path path = new Path(); // Reuse path object

        for (ScoreTypeConfig scoreType : scoreTypesToDisplay) {
            path.reset();
            linePaint.setColor(scoreType.color);
            boolean firstPointInView = true;

            // Iterate backwards to find the first point that should be on screen or just off-screen to the right
            for (int i = currentScores.size() - 1; i >= 0; i--) {
                TimedScoreResult tsr = currentScores.get(i);
                int scoreValue = scoreType.extractor.extract(tsr.result);

                long timeDeltaFromLatest = latestTimestamp - tsr.timestamp;

                // Calculate X: 0ms delta is at graphRight, VISIBLE_TIME_WINDOW_MS delta is at graphLeft
                float x = graphRight - (timeDeltaFromLatest * pixelsPerMs);

                // Calculate Y: Score 0 is at graphBottom, Score 100 is at graphTop
                float y = graphBottom - (scoreValue * pixelsPerScorePoint);
                y = Math.max(graphTop, Math.min(y, graphBottom)); // Clamp Y to graph bounds


                if (firstPointInView) {
                    path.moveTo(x, y);
                    firstPointInView = false;
                } else {
                    path.lineTo(x, y);
                }

                // Optimization: if x is already far left of the graph,
                // and we have drawn at least one segment, we can break early.
                // (add a little buffer like graphWidth to ensure lines connect smoothly from off-screen)
                if (x < graphLeft - graphWidth && i < currentScores.size() - 2) { // Check i to ensure we have history
                    // Need to draw one more point that is off-screen to the left to complete the line segment
                    if (i > 0) {
                        TimedScoreResult prevTsr = currentScores.get(i-1);
                        int prevScoreValue = scoreType.extractor.extract(prevTsr.result);
                        long prevTimeDelta = latestTimestamp - prevTsr.timestamp;
                        float prevX = graphRight - (prevTimeDelta * pixelsPerMs);
                        float prevY = graphBottom - (prevScoreValue * pixelsPerScorePoint);
                        prevY = Math.max(graphTop, Math.min(prevY, graphBottom));
                        path.lineTo(prevX, prevY);
                    }
                    break;
                }
            }
            if (!firstPointInView) { // if path has data
                canvas.drawPath(path, linePaint);
            }
        }

        // 5. Draw Legend
        drawLegend(canvas);
    }

    private void drawEmptyState(Canvas canvas) {
        textPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText("Waiting for score data...",
                canvas.getWidth() / 2f,
                canvas.getHeight() / 2f,
                textPaint);
    }

    private void drawLegend(Canvas canvas) {
        float legendY = graphBottom + graphMarginPx + legendPaddingPx; // Start Y for legend items
        float currentX = graphLeft;

        Paint.FontMetrics fm = textPaint.getFontMetrics();
        float textHeight = fm.descent - fm.ascent;
        float legendItemHeight = Math.max(legendSwatchSizePx, textHeight);


        for (ScoreTypeConfig scoreType : scoreTypesToDisplay) {
            // Check if there's enough space for this legend item
            float swatchAndTextWidth = legendSwatchSizePx + legendPaddingPx + textPaint.measureText(scoreType.name);
            if (currentX + swatchAndTextWidth > graphRight) {
                // Move to next line (not implemented here for simplicity, assumes one line legend)
                // For multi-line legend, you'd increment legendY and reset currentX
                break; // Stop if out of horizontal space
            }

            // Draw color swatch
            legendPaint.setColor(scoreType.color);
            float swatchTop = legendY + (legendItemHeight - legendSwatchSizePx) / 2f;
            canvas.drawRect(currentX, swatchTop, currentX + legendSwatchSizePx, swatchTop + legendSwatchSizePx, legendPaint);
            currentX += legendSwatchSizePx + legendPaddingPx;

            // Draw text
            textPaint.setTextAlign(Paint.Align.LEFT);
            textPaint.setColor(TEXT_COLOR); // Ensure legend text is visible
            // Adjust Y for text baseline
            float textY = legendY + (legendItemHeight - textHeight)/2f - fm.ascent;
            canvas.drawText(scoreType.name, currentX, textY, textPaint);
            currentX += textPaint.measureText(scoreType.name) + legendPaddingPx * 2; // Extra padding between items
        }
    }


    // --- Utility Methods ---
    private float dpToPx(Context context, float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics());
    }

    private float spToPx(Context context, float sp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, context.getResources().getDisplayMetrics());
    }

    // --- Lifecycle (optional, for pausing/resuming if needed beyond surface) ---
    public void pause() {
        isDrawing = false;
        try {
            if (drawingThread != null) {
                drawingThread.join();
            }
        } catch (InterruptedException e) {
            Log.e(TAG, "Error pausing drawing thread", e);
        }
        drawingThread = null;
    }

    public void resume() {
        if (surfaceHolder.getSurface() != null && surfaceHolder.getSurface().isValid()) {
            isDrawing = true;
            drawingThread = new Thread(this);
            drawingThread.start();
        }
        // If surface is not yet ready, surfaceCreated will handle starting the thread.
    }
}